在 3D 圖形渲染和資料視覺化中,效能是關鍵問題之一。隨著資料量的增大,若繼續使用高階語言提供的內建陣列和資料結構,雖然靈活方便,通常會造成效能瓶頸。為了處理大量同質性的數據,JavaScript 提供了 TypedArray,使得我們能夠更高效地管理和操作數據。
今天,將延續昨天的架構,繼續講解如何在 Three.js 中自定義 3D 長條圖。
如果有學過 C++ 或其他低階語言,就會知道在定義變數時,通常會先定義型別,同理陣列也一樣,由於提前分配固定的記憶體空間,並且密集排列,存取就能更加高效。
而 Javascript 以自由聞名,動態設計了基本類型 Array,並提供了不少靈活操作陣列的原生方法,在普通陣列中,元素實際上是一個指針,指向不同內存位置,這樣就能儲存不同類型的數據。相當於多了一個物流中心,管理多個不同類型的倉庫,比起單一倉庫來說,存取要經過更多步驟。
因此,在操作大量同型別的數據時,我們仍需要更高效的資料結構,為此 JavaScript 提供了多種 TypedArray 類型,例如:
像是昨天的頻譜分析,就使用 Uint8Array 來處理音訊;接下來要介紹的圖形渲染場景,尤其是基於 WebGL 的應用 Three.js,就會大量使用 Float32Array 來定義幾何頂點座標。
作為 Three 中, Mesh 是構建 3D 物件的核心,我們需要考慮以下幾點:
在繪製 3D 長條圖的時候,我們的目的不是製作一個真實的場景,因此我們可以考慮無光環境,並且為長條圖自定義顏色。
為此,我們採用以下的幾何體和材質來構建 3D 實體:
我們設計了一個類別 BufferFactory,用來生成 3D 長條圖,並用井字號區分私有變數。這允許我們靈活生成和管理多個長條圖,如下設計了一座長度為 360 的工廠陣列,可以產出並容納 6 秒內的所有長條圖。
class BufferFactory{
#material;
#factorys;
constructor(){
this.#material = new THREE.MeshBasicMaterial({
vertexColors: true,
});
this.mesh = new THREE.Group();
this.#factorys = new Array(360).fill(0).map(()=>this.createFactory());
}
createFactory(){
const factory = {
'geometry': new THREE.BufferGeometry(),
'mesh': null
};
factory.mesh = new THREE.Mesh( factory.geometry, this.#material )
this.mesh.add(factory.mesh);
return factory;
}
}
首先回顧昨天頻譜分析後,是如何傳遞資料的:
const createMusicAnalyser = function(){
this.setCanvas = (canvas) => {
//......
this.buff = new BufferFactory();
this.scene.add(this.buff.mesh)
}
this.update = () => {
if(this.analyser){
//......
this.buff.transformData(data);
}
this.buff.update();
}
return this;
}
在構築幾何體時,需要提供對應的頂點和屬性(明天會詳細說明),於是我們分成三步驟,先按照直覺,準備長條圖六個面的向量數據(vector),再將其轉換為頂點數據(vertices),最後包裝成三個座標一組的屬性(attribute)。
class BufferFactory{
transformData(data){
// 循環陣列
const factory = this.#factorys.shift();
this.#factorys.push(factory);
// 計算頂點座標
const vector = this.getPosition(data);
const vertices = this.getVertices(vector);
const attribute = new THREE.BufferAttribute(vertices, 3);
factory.geometry.setAttribute('position', attribute);
// 計算頂點顏色
const colorVertices = this.getColorVertices(data, vertices);
const colorAttribute = new THREE.BufferAttribute(colorVertices, 3)
factory.geometry.setAttribute('color', colorAttribute);
}
}
當添加新的圖形時,我們希望它像水一樣,不斷流下去,直到中斷處消失。為達成這個效果,我們才將首個元素放入末尾,並且更新覆蓋它的圖形。接著,在每次更新時根據索引值來分配Z軸座標,就能實現不斷循環更新的長條圖了。
class BufferFactory{
#depth = 2;
updateFactorys(){
const len = this.#factorys.length;
this.#factorys.forEach((factory, index)=>{
factory.mesh.position.set(0, 0, this.#depth * (index - len));
});
}
update(){
this.updateFactorys();
}
}
本文探討了 TypedArray 在 3D 圖形渲染中的應用,特別是如何使用 Three.js 結合 Float32Array 高效地生成和更新長條圖。透過簡單的工廠設計模式,我們成功構建了一個能夠處理實時資料的圖形渲染系統。明天,我們將詳細探討頂點和圖形的關係,你將學會如何利用頂點繪製圖形,並且完整演算法的封裝。
如果感興趣,可以參考 Github 上的原始碼: